package com.share.system.api.service;

import com.share.system.api.domain.Station;
import com.share.system.api.domain.StationLocation;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.domain.geo.Metrics;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * todo (后续改进)大量的redis操作都没有加入事务，存在不一致风险（原子性问题）
 * todo 优化: 存在缓存不一致问题，区域父子级关系，层级结构，失效之后无法即使更新（原子性问题）
 */
@Service
@Slf4j
public class StationRedisService {

    @Autowired
    @Qualifier("stringRedisTemplate")
    private RedisTemplate<String, String> redisTemplate;

    @Qualifier("geoRedisTemplate")
    @Autowired
    private RedisTemplate<String, String> geoRedisTemplate;

    // 在类中定义常量以便复用
    private static final String STATION_HASH_KEY_PREFIX = "station:info:";
    private static final String STATION_HOTGEO_KEY = "station:locations:hot";
    private static final String STATION_ALLGEO_KEY = "station:locations:all";
    private static final int STATION_CACHE_EXPIRE_TIME = 3600; // 1小时过期时间

    /**
     * 热点数据的更新：在类中添加预编译脚本
     * 即缓存站详细信息又站点经纬度信息
      */
    private static final String SAVE_STATION_LUA =
            "local stationId = ARGV[1] " +
            "local hashKey = '" + STATION_HASH_KEY_PREFIX + "' .. stationId " +
            "local geoKey = '" + STATION_HOTGEO_KEY + "' " +
            "local longitude = ARGV[2] " +
            "local latitude = ARGV[3] " +
            "local newVersion = ARGV[4] " +  // 新版本号作为第4个参数
            // 动态处理Hash字段（从第5个参数开始）
            "local hmsetArgs = {hashKey} " +
            "for i = 5, #ARGV, 2 do " +
            "  if ARGV[i] and ARGV[i+1] then " +
            "    table.insert(hmsetArgs, ARGV[i]) " +
            "    table.insert(hmsetArgs, ARGV[i+1]) " +
            "  end " +
            "end " +
            // 版本号检查：如果当前版本号大于等于新版本号，则不更新
            "local currentVersion = redis.call('HGET', hashKey, 'version') " +
            "if currentVersion and tonumber(currentVersion) >= tonumber(newVersion) then " +
            "  return 0 " +  // 返回0表示未更新
            "end " +
            // 更新Hash数据
            "redis.call('HMSET', unpack(hmsetArgs)) " +
            // 更新GEO数据
            "redis.call('GEOADD', geoKey, longitude, latitude, stationId) " +
            // 设置过期时间
            "redis.call('EXPIRE', hashKey, " + STATION_CACHE_EXPIRE_TIME + ") " +
            "return 1";  // 返回1表示更新成功

    /**
     * 缓存站点经纬度信息和站点id和更新时间戳
     * 查询采取的策略是
     *  查询热点数据:
         *  如果存在，将热点数据和全量数据进行比对判断是否为最新数据
         *  如果是则返回，如果不是则查询数据库
         *  如果热点数据不存在，查询全量缓存数据
         *  如果全量缓存数据不存在，则查询数据库
     */

    /**
     * lua脚本脚本类
     */
    private final DefaultRedisScript<Long> updateWithVersionCheckScript = new DefaultRedisScript<>();

    @PostConstruct
    public void init() {
        // 初始化预编译脚本
        updateWithVersionCheckScript.setScriptText(SAVE_STATION_LUA);
        updateWithVersionCheckScript.setResultType(Long.class);

    }

//    public StationRedisService(@Qualifier("stringRedisTemplate") RedisTemplate<String, String> redisTemplate,
//                               @Qualifier("geoRedisTemplate") RedisTemplate<String, String> geoRedisTemplate) {
//        this.redisTemplate = redisTemplate;
//        this.geoRedisTemplate = geoRedisTemplate;
//    }

    /**
     * 构建区域信息Map
     */
    private Map<String, String> buildStationInfoMap(Station station) {
        Map<String, String> stationInfo = new HashMap<>();
        stationInfo.put("id", String.valueOf(station.getId()));
        stationInfo.put("name", station.getName());
        stationInfo.put("image_url", station.getImageUrl());
        stationInfo.put("business_hours", station.getBusinessHours());
        stationInfo.put("longitude", String.valueOf(station.getLongitude()));
        stationInfo.put("latitude", String.valueOf(station.getLatitude()));
        stationInfo.put("province_code", station.getProvinceCode());
        stationInfo.put("city_code", station.getCityCode());
        stationInfo.put("district_code", station.getDistrictCode());
        stationInfo.put("address", station.getAddress());
        stationInfo.put("full_address", station.getFullAddress());
        stationInfo.put("head_name", station.getHeadName());
        stationInfo.put("head_phone", station.getHeadPhone());
        stationInfo.put("cabinet_id", String.valueOf(station.getCabinetId()));
        stationInfo.put("fee_rule_id", String.valueOf(station.getCreateTime()));
        stationInfo.put("status", station.getStatus());
        stationInfo.put("cabinetNo", String.valueOf(station.getCabinetNo()));
        stationInfo.put("feeRuleName", String.valueOf(station.getCabinetNo()));
        stationInfo.put("createBy", station.getCreateBy());
        stationInfo.put("updateBy", station.getUpdateBy());
        stationInfo.put("updateTime", String.valueOf(station.getUpdateTime()));
        stationInfo.put("delFlag", station.getDelFlag());
        stationInfo.put("remark", station.getRemark());
        if (station.getVersion() != null){
            stationInfo.put("version", String.valueOf(station.getVersion()));
        }
        return stationInfo;
    }


    /**
     * 保存站点信息，一个站点详细信息，一个站点经纬度信息
     * @param station
     * @return
     */
    public boolean saveStation(Station station) {
        try {
            Map<String, String> stationInfo = buildStationInfoMap(station);

            List<String> args = new ArrayList<>();
            args.add(String.valueOf(station.getId()));
            args.add(String.valueOf(station.getLongitude()));
            args.add(String.valueOf(station.getLatitude()));

            // 将map转换为键值对参数
            for (Map.Entry<String, String> entry : stationInfo.entrySet()) {
                args.add(entry.getKey());
                args.add(entry.getValue());
            }
            Long result = redisTemplate.execute(updateWithVersionCheckScript, Collections.emptyList(),
                    args.toArray(new String[0]));
            return result != null && result > 0;
        } catch (Exception e) {
            log.error("保存站点信息失败: {}", station.getId(), e);
            return false;
        }
    }

    /**
     * 删除站点缓存信息
     * @param stationId
     * @return
     */
    public boolean removeStation(Long stationId) {
        try {
            String hashKey = "station:info:" + stationId;
            redisTemplate.opsForHash().delete(hashKey);
            redisTemplate.opsForGeo().remove(STATION_HOTGEO_KEY, String.valueOf(stationId));
            return true;
        } catch (Exception e) {
            log.error("删除站点信息失败: {}", stationId, e);
            return false;
        }
    }


    /**
     * 热点站点经纬度定义分片key
     * @param station
     * @return
     */
    private String getHotGeoKey(Station station) {
        // 可以按城市编码、省份编码等进行分片
        return STATION_HOTGEO_KEY + (station.getCityCode() != null ? station.getCityCode() : "default");
    }

    /**
     * 全量站点经纬度定义分片key
     * @param station
     * @return
     */
    private String getAllGeoKey(Station station) {
        // 可以按城市编码、省份编码等进行分片
        return STATION_ALLGEO_KEY + (station.getCityCode() != null ? station.getCityCode() : "default");
    }



    /**
     * 批量处理站点数据
     * 增量存储在热点数据中
     * 而全量存储在全量数据中
     */
    public void batchSaveStationsWithPipeline(List<Station> stations) {
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Station station : stations) {
                if (station.getLongitude() != null && station.getLatitude() != null) {
                    // 存储到全量GEO key中
                    connection.geoAdd(STATION_ALLGEO_KEY.getBytes(),
                            new Point(station.getLongitude().doubleValue(), station.getLatitude().doubleValue()),
                            String.valueOf(station.getId()).getBytes());
                }
            }
            return null;
        });
    }

    /**
     * 查询附近站点，先查询增量数据，然后查询全量数据，然后进行去重，返回站点经纬度对象即可
     * @param longitude 经度
     * @param latitude 纬度
     * @param radius 搜索半径(米)
     * @return 附近站点列表
     */
    public List<StationLocation> findNearbyStationsWithCompensation(
            Double longitude, Double latitude, Double radius) {
        List<StationLocation> locations = new ArrayList<>();

        // 1. 配置查询参数
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
                .newGeoRadiusArgs()
                .includeCoordinates()   // 必须：包含坐标
                .includeDistance()      // 推荐：包含距离
                .sortAscending();       // 推荐：按距离排序
        // 先查询热点数据 - 使用正确的GeoLocation<String>类型
        GeoResults<GeoLocation<String>> hotResults =
                geoRedisTemplate.opsForGeo()
                        .radius(STATION_HOTGEO_KEY,
                                new Circle(new Point(longitude, latitude),
                                        new Distance(radius, Metrics.METERS)),args);

        if (hotResults != null && !hotResults.getContent().isEmpty()) {
            for (GeoResult<GeoLocation<String>> result : hotResults) {
                StationLocation stationLocation = convertToStationLocation(result);
                if (stationLocation != null) {
                    locations.add(stationLocation);
                }
            }
        }

        // 热点数据不存在，查询全量数据
        GeoResults<GeoLocation<String>> allResults = geoRedisTemplate.opsForGeo()
                .radius(STATION_ALLGEO_KEY,
                        new Circle(new Point(longitude, latitude),
                                new Distance(radius, Metrics.METERS)),args);

        if (allResults != null && !allResults.getContent().isEmpty()) {
            // 将GeoResults转换为StationLocation列表
            List<StationLocation> stationLocations = convertToStationLocations(allResults.getContent());
            // 获取locations中已存在的stationId集合
            Set<Long> existingIds = locations.stream()
                    .map(StationLocation::getStationId)
                    .collect(Collectors.toSet());
            // 只添加stationLocations中不重复的元素
            stationLocations.stream()
                    .filter(stationLocation -> !existingIds.contains(stationLocation.getStationId()))
                    .forEach(locations::add);
            return locations;
        }else if (allResults == null || allResults.getContent().isEmpty()) {
            // 全量数据也不存在，需要查询数据库并补偿缓存
//            throw new RuntimeException("全量数据不存在,请联系管理员");
            log.error("全量数据不存在,请联系管理员");
            return null;
        }
        return null;
    }

    /**
     * 转换Geo结果为站点位置对象
     */
    private List<StationLocation> convertToStationLocations(List<GeoResult<GeoLocation<String>>> results) {
        List<StationLocation> locations = new ArrayList<>();
        for (GeoResult<GeoLocation<String>> result : results) {
            locations.add(convertToStationLocation(result));
        }
        return locations;
    }

    /**
     * 转换单个Geo结果为站点位置对象
     */
    private StationLocation convertToStationLocation(GeoResult<GeoLocation<String>> result) {
        StationLocation location = new StationLocation();
        location.setStationId(Long.valueOf(result.getContent().getName()));
        // 获取坐标点
        Point point = result.getContent().getPoint();
        location.setLocation(new GeoJsonPoint(point.getX(), point.getY()));
        return location;
    }


}
